package tk.winshu.shortestpath.view;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.winshu.shortestpath.model.Node;
import tk.winshu.shortestpath.model.NodeData;
import tk.winshu.shortestpath.util.ExportUtil;
import tk.winshu.shortestpath.util.ImportUtil;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * @author Jason Krebs
 * @date 2015年2月3日
 */
public class MainFrame extends JFrame {

    private static final Logger log = LoggerFactory.getLogger(MainFrame.class.getName());
    private static final String ABOUT_DESCRIPTION = "作者 : Kevin Hu\n日期 : 2015.02.11\n更新 : 2018.08.16\n更新 : 2019.05.28\n更新 : 2019.10.19\n更新 : 2019.10.29";

    /**
     * 画板
     */
    private CanvasPanel canvasPanel;

    /**
     * 绘制节点/连线
     */
    private JRadioButton drawNodeRadio;
    /**
     * 移动节点
     */
    private JRadioButton moveNodeRadio;

    /**
     * 是否有向图
     */
    private JCheckBox directedGraphCheckbox;
    /**
     * 显示权重
     */
    private JCheckBox showWeightCheckbox;
    /**
     * 循环运行
     */
    private JCheckBox keepLoopRunningCheckbox;

    /**
     * 运行/停止
     */
    private JButton runOrStopButton;
    /**
     * 调换起点/终点
     */
    private JButton exchangeButton;
    /**
     * 清除所有连线
     */
    private JButton cleanLinesButton;
    /**
     * 清除所有
     */
    private JButton cleanAllButton;

    /**
     * 右键菜单
     */
    private JPopupMenu popupMenu;
    /**
     * 提示
     */
    private JLabel tipLabel;

    public MainFrame() {
        setTitle("最短路径寻找算法测试");
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setIconImage(CanvasPanel.NODE_IMAGE);

        createMenu();
        createContent();
        createPopupMenu();

        addListener();
        init();
    }

    private void addListener() {
        new DropTarget(canvasPanel, new DropTargetAdapter() {
            @Override
            public void drop(DropTargetDropEvent dropEvent) {
                onDrop(dropEvent);
            }
        });
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
                    moveNodeRadio.setSelected(true);
                }
            }

            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
                    drawNodeRadio.setSelected(true);
                }
            }
        });
        canvasPanel.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                onMouseDragged(e);
            }
        });
        canvasPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                onMouseClick(e);
            }
        });
        canvasPanel.registerRunningEvent(() -> updateControl(false));
        showWeightCheckbox.addChangeListener(e -> canvasPanel.setWeightVisible(showWeightCheckbox.isSelected()));
        keepLoopRunningCheckbox.addChangeListener(e -> canvasPanel.setKeepLoopRunning(keepLoopRunningCheckbox.isSelected()));
        directedGraphCheckbox.addChangeListener(e -> canvasPanel.setDirectedGraph(directedGraphCheckbox.isSelected()));

        runOrStopButton.addActionListener(e -> runOrStop());
        exchangeButton.addActionListener(e -> canvasPanel.exchange());
        cleanLinesButton.addActionListener(e -> canvasPanel.cleanLines());
        cleanAllButton.addActionListener(e -> canvasPanel.cleanAll());
    }

    private void createContent() {
        JPanel contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(new BorderLayout(0, 0));

        canvasPanel = new CanvasPanel();
        canvasPanel.setPreferredSize(new Dimension(600, 500));

        canvasPanel.setForeground(Color.WHITE);
        canvasPanel.setBackground(Color.BLACK);
        canvasPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));

        JScrollPane scrollPane = new JScrollPane(canvasPanel);
        contentPane.add(scrollPane, BorderLayout.CENTER);

        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        contentPane.add(toolBar, BorderLayout.SOUTH);

        tipLabel = new JLabel("");
        toolBar.add(tipLabel);

        JPanel controlPanel = new JPanel();
        contentPane.add(controlPanel, BorderLayout.EAST);

        GridBagLayout gblControlPanel = new GridBagLayout();
        gblControlPanel.columnWidths = new int[]{175, 0};
        gblControlPanel.rowHeights = new int[]{130, 130, 240};
        gblControlPanel.columnWeights = new double[]{1.0, Double.MIN_VALUE};
        gblControlPanel.rowWeights = new double[]{1.0, 1.0, 3.0};
        controlPanel.setLayout(gblControlPanel);

        JPanel drawPanel = new JPanel();
        drawPanel.setBorder(new TitledBorder(null, "模式", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        GridBagConstraints gbcDrawPanel = new GridBagConstraints();
        gbcDrawPanel.fill = GridBagConstraints.BOTH;
        gbcDrawPanel.insets = new Insets(0, 0, 5, 0);
        gbcDrawPanel.gridx = 0;
        gbcDrawPanel.gridy = 0;
        controlPanel.add(drawPanel, gbcDrawPanel);
        drawPanel.setLayout(new BoxLayout(drawPanel, BoxLayout.Y_AXIS));

        drawNodeRadio = new JRadioButton("绘制节点/连线");
        drawPanel.add(drawNodeRadio);

        moveNodeRadio = new JRadioButton("移动节点");
        drawPanel.add(moveNodeRadio);

        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(drawNodeRadio);
        buttonGroup.add(moveNodeRadio);

        JPanel extendPanel = new JPanel();
        extendPanel.setBorder(new TitledBorder(null, "扩展", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        GridBagConstraints gbcExtendPanel = new GridBagConstraints();
        gbcExtendPanel.fill = GridBagConstraints.HORIZONTAL;
        gbcExtendPanel.insets = new Insets(0, 0, 5, 0);
        gbcExtendPanel.gridx = 0;
        gbcExtendPanel.gridy = 1;
        extendPanel.setLayout(new BoxLayout(extendPanel, BoxLayout.Y_AXIS));
        controlPanel.add(extendPanel, gbcExtendPanel);

        showWeightCheckbox = new JCheckBox("显示权重");
        extendPanel.add(showWeightCheckbox);

        directedGraphCheckbox = new JCheckBox("有向图");
        extendPanel.add(directedGraphCheckbox);

        keepLoopRunningCheckbox = new JCheckBox("循环运行");
        extendPanel.add(keepLoopRunningCheckbox);

        JPanel operatePanel = new JPanel();
        operatePanel.setBorder(new TitledBorder(null, "操作", TitledBorder.LEADING, TitledBorder.TOP, null, null));
        GridBagConstraints gbcOperatePanel = new GridBagConstraints();
        gbcOperatePanel.anchor = GridBagConstraints.NORTH;
        gbcOperatePanel.fill = GridBagConstraints.HORIZONTAL;
        gbcOperatePanel.gridx = 0;
        gbcOperatePanel.gridy = 2;
        controlPanel.add(operatePanel, gbcOperatePanel);
        GridBagLayout gblOperatePanel = new GridBagLayout();
        gblOperatePanel.columnWeights = new double[]{0.0};
        operatePanel.setLayout(gblOperatePanel);

        runOrStopButton = new JButton("运行");
        GridBagConstraints gbcRunOrStopButton = new GridBagConstraints();
        gbcRunOrStopButton.fill = GridBagConstraints.BOTH;
        gbcRunOrStopButton.insets = new Insets(0, 0, 5, 0);
        gbcRunOrStopButton.gridx = 0;
        gbcRunOrStopButton.gridy = 0;
        operatePanel.add(runOrStopButton, gbcRunOrStopButton);
        runOrStopButton.setForeground(Color.BLUE);

        exchangeButton = new JButton("调换");
        exchangeButton.setToolTipText("交换起点和终点");
        GridBagConstraints gbcExchangeButton = new GridBagConstraints();
        gbcExchangeButton.fill = GridBagConstraints.HORIZONTAL;
        gbcExchangeButton.insets = new Insets(0, 0, 5, 0);
        gbcExchangeButton.gridx = 0;
        gbcExchangeButton.gridy = 1;
        operatePanel.add(exchangeButton, gbcExchangeButton);

        cleanLinesButton = new JButton("清空连线");
        GridBagConstraints gbcCleanLinesButton = new GridBagConstraints();
        gbcCleanLinesButton.fill = GridBagConstraints.HORIZONTAL;
        gbcCleanLinesButton.insets = new Insets(0, 0, 5, 0);
        gbcCleanLinesButton.gridx = 0;
        gbcCleanLinesButton.gridy = 2;
        operatePanel.add(cleanLinesButton, gbcCleanLinesButton);

        cleanAllButton = new JButton("清空所有");
        GridBagConstraints gbcClearAllButton = new GridBagConstraints();
        gbcClearAllButton.fill = GridBagConstraints.BOTH;
        gbcClearAllButton.gridx = 0;
        gbcClearAllButton.gridy = 3;
        operatePanel.add(cleanAllButton, gbcClearAllButton);
        cleanAllButton.setForeground(Color.RED);
    }

    private void createMenu() {
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        JMenu fileMenu = new JMenu("文件");
        menuBar.add(fileMenu);

        JMenuItem importMenuItem = new JMenuItem("导入");
        importMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_MASK));
        importMenuItem.addActionListener(e -> doImport());
        fileMenu.add(importMenuItem);

        JMenuItem exportMenuItem = new JMenuItem("导出");
        exportMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));
        exportMenuItem.addActionListener(e -> doExport());
        fileMenu.add(exportMenuItem);

        JMenuItem rawImageItem = new JMenuItem("设置底图");
        rawImageItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, InputEvent.CTRL_MASK));
        rawImageItem.addActionListener(e -> doSetRawImage());
        fileMenu.add(rawImageItem);

        JMenuItem exitMenuItem = new JMenuItem("退出");
        exitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.ALT_MASK));
        exitMenuItem.addActionListener(e -> MainFrame.this.dispose());
        fileMenu.add(exitMenuItem);

        JMenu editMenu = new JMenu("编辑");
        menuBar.add(editMenu);

        JMenuItem previousMenuItem = new JMenuItem("上一步");
        previousMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK));
        previousMenuItem.addActionListener(e -> canvasPanel.historyToPrevious());
        editMenu.add(previousMenuItem);

        JMenuItem nextMenuItem = new JMenuItem("下一步");
        nextMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK));
        nextMenuItem.addActionListener(e -> canvasPanel.historyToNext());
        editMenu.add(nextMenuItem);

        JMenu helpMenu = new JMenu("帮助");
        menuBar.add(helpMenu);

        JMenuItem informationMenuItem = new JMenuItem("信息");
        informationMenuItem.addActionListener(e -> new HelpDialog().setVisible(true));
        helpMenu.add(informationMenuItem);

        JMenuItem aboutMenuItem = new JMenuItem("关于");
        aboutMenuItem.addActionListener(e -> JOptionPane.showMessageDialog(MainFrame.this, ABOUT_DESCRIPTION));
        helpMenu.add(aboutMenuItem);


    }

    private void createPopupMenu() {
        popupMenu = new JPopupMenu();

        // 设置起点
        JMenuItem setStartMenuItem = new JMenuItem("设为起点");
        setStartMenuItem.addActionListener(e -> canvasPanel.markFocusedAsStartNode());
        popupMenu.add(setStartMenuItem);

        // 设置终点
        JMenuItem setEndMenuItem = new JMenuItem("设为终点");
        setEndMenuItem.addActionListener(e -> canvasPanel.markFocusedAsEndNode());
        popupMenu.add(setEndMenuItem);

        popupMenu.addSeparator();

        // 移除节点
        JMenuItem evictNodeMenuItem = new JMenuItem("移除节点");
        evictNodeMenuItem.addActionListener(e -> canvasPanel.removeNode());
        popupMenu.add(evictNodeMenuItem);

        // 移除连线
        JMenuItem evictLineMenuItem = new JMenuItem("移除连线");
        evictLineMenuItem.addActionListener(e -> {
            if (!canvasPanel.removeLine()) {
                tipLabel.setText("请选择一个节点");
            }
        });
        popupMenu.add(evictLineMenuItem);

        // 增强弹出菜单监听
        canvasPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (canvasPanel.isRunning()) {
                    return;
                }
                if (e.getButton() == MouseEvent.BUTTON3) {
                    Node node = canvasPanel.getNode(e.getPoint());
                    if (node != null && e.isPopupTrigger()) {
                        popupMenu.show(e.getComponent(), e.getX(), e.getY());
                    }
                }
            }
        });
    }

    private void init() {
        drawNodeRadio.setSelected(true);
        directedGraphCheckbox.setSelected(true);
        canvasPanel.setDirectedGraph(true);
        keepLoopRunningCheckbox.setSelected(true);
        canvasPanel.setKeepLoopRunning(true);

        setFocusable(true);
    }

    /**
     * 处理拖拽事件
     */
    private void onDrop(DropTargetDropEvent dropEvent) {
        try {
            if (!dropEvent.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                dropEvent.rejectDrop();
                return;
            }

            dropEvent.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
            List<?> list = (List<?>) (dropEvent.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
            for (Object aList : list) {
                File f = (File) aList;
                String path = f.getAbsolutePath().toLowerCase();
                if (path.endsWith(".json")) {
                    NodeData nodeData = ImportUtil.importFrom(path);
                    canvasPanel.loadNodes(nodeData);
                    tipLabel.setText(String.format("导入成功！节点数=%d", nodeData.getNodeCount()));
                }
            }
            dropEvent.dropComplete(true);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    private void doExport() {
        if (canvasPanel.getNodeCount() == 0) {
            tipLabel.setText("没有任何节点，不能导出");
            return;
        }
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("导出...");
        chooser.setDialogType(JFileChooser.SAVE_DIALOG);
        chooser.setSelectedFile(new File("nodes"));
        chooser.setFileFilter(new FileNameExtensionFilter("*.json", "json"));

        if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
            File selected = chooser.getSelectedFile();
            if (selected == null) {
                return;
            }

            String path = selected.getPath();
            if (!selected.getPath().contains(".")) {
                FileNameExtensionFilter currentFilter = (FileNameExtensionFilter) chooser.getFileFilter();
                path += "." + currentFilter.getExtensions()[0];
            }
            NodeData config = new NodeData();
            ExportUtil.exportTo(canvasPanel.buildNodeData(), path);
            tipLabel.setText(String.format("导出成功！节点数=%d", canvasPanel.getNodeCount()));
        }
    }

    private void doImport() {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("导入数据...");
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setFileFilter(new FileNameExtensionFilter("*.json", "json"));

        if (chooser.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {
            File selected = chooser.getSelectedFile();
            if (selected != null) {
                String path = selected.getPath();
                NodeData nodeData = ImportUtil.importFrom(path);
                canvasPanel.loadNodes(nodeData);
                tipLabel.setText(String.format("导入成功！节点数=%d", nodeData.getNodeCount()));
            }
        }
    }

    private void doSetRawImage() {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("选择底图...");
        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setFileFilter(new FileNameExtensionFilter("*.jpg|*.png", "jpg", "png"));

        if (chooser.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {
            File selected = chooser.getSelectedFile();
            if (selected != null && selected.exists()) {
                try {
                    canvasPanel.setRawImage(ImageIO.read(selected));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void onMouseClick(MouseEvent e) {
        if (canvasPanel.isRunning()) {
            return;
        }
        if (e.getButton() == MouseEvent.BUTTON1) {
            if (drawNodeRadio.isSelected()) {
                canvasPanel.createNode(e.getPoint());
            }
        } else if (e.getButton() == MouseEvent.BUTTON3) {
            if (canvasPanel.getFocusedNode() == null) {
                return;
            }
            if (e.isPopupTrigger()) {
                popupMenu.show(e.getComponent(), e.getX(), e.getY());
            }
        }
    }

    private void onMouseDragged(MouseEvent e) {
        if (canvasPanel.isRunning()) {
            return;
        }
        if (moveNodeRadio.isSelected()) {
            canvasPanel.moveNode(e.getPoint());
        }
    }

    private void runOrStop() {
        boolean isRunnable = canvasPanel.isRunnable();
        updateControl(isRunnable);
        if (isRunnable) {
            canvasPanel.runAction();
        } else {
            canvasPanel.stopAction();
        }
        // 点击过快，会导致重复创建线程
    }

    private void updateControl(boolean isReadyToRun) {
        boolean enabled = !isReadyToRun;
        drawNodeRadio.setEnabled(enabled);
        moveNodeRadio.setEnabled(enabled);
        showWeightCheckbox.setEnabled(enabled);
        keepLoopRunningCheckbox.setEnabled(enabled);
        directedGraphCheckbox.setEnabled(enabled);
        cleanLinesButton.setEnabled(enabled);
        cleanAllButton.setEnabled(enabled);
        exchangeButton.setEnabled(enabled);
        popupMenu.setEnabled(enabled);

        runOrStopButton.setText(isReadyToRun ? "停止" : "运行");
    }
}
